/*
	flad : Copyright 2010 Dale J Williams

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
	
	Portions of this class was written with guidance from the blog of 
	Andre Michelle - http://blog.andre-michelle.com/
*/
package lads.flad.sound {
	
	import flash.events.SampleDataEvent;
	import flash.events.TimerEvent;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	import lads.flad.GameCore;
	import flash.events.Event;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	
	/**
	 * SoundObject: Contains and controls a sound, channel.
	 * @author Dale J Williams
	 * @version 1.0
	 */
	public class SoundObject {
		
		protected const GAP			:Number = 576;
		protected const FRONT_DELAY	:Number = GAP*3;		
		protected const BACK_DELAY	:Number = GAP*4;		
		protected const BUFFER_SIZE	:int = 4096;
		
		protected var _manager		:SoundManager;
		protected var _track		:SoundTrack;
		protected var _name			:String;
		
		protected var _volume		:Number;
		protected var _volumeAbs	:Number;
		
		protected var _sound		:Sound;
		protected var _channel		:SoundChannel;
		protected var _transform	:SoundTransform;
		
		protected var _playing		:Boolean;
		protected var _looping		:Boolean;
		protected var _initVolume	:Number;
		protected var _fadingOut	:Boolean;		
		protected var _loopBegin	:int;
		protected var _loopEnd		:int;
		
		protected var _target		:ByteArray;
		protected var _position		:Number;
		protected var _output		:Sound;	
		
		public function SoundObject($name:String, $urlOrSound:Object=null, $volume:Number=1, $looping:Boolean=false) {
			_name = $name;
			_volume = _volumeAbs = $volume;
			_looping = $looping;
			if ($urlOrSound) load($urlOrSound);
			init();
		}
		
		private function init():void {
			_channel = new SoundChannel();
			_transform = new SoundTransform();
			_target = new ByteArray();
			_position = 0;
			_output = new Sound();
		}
		
		public function load($urlOrSound:Object):void {
			if ($urlOrSound is String) {
				_sound = new Sound(new URLRequest(String($urlOrSound)));
			} else if ($urlOrSound is Sound) {
				_sound = Sound($urlOrSound);
			} else throw new Error("SoundObject must be loaded from Sound (in-built) or URL (String)");
		}
		
		public function play():void {
			if (!_sound) throw new Error("SoundObject not loaded.");
			if (_playing) close();
			doPlay();
			updateVolume();
		}

		private function doPlay():void {
			_playing = true;
				try {
					if (_looping) {
						_output.addEventListener(SampleDataEvent.SAMPLE_DATA, sampleData);
						_channel = _output.play();
					} else {
						_channel.addEventListener(Event.SOUND_COMPLETE, playComplete);
						_channel = _sound.play();
					}
				} catch (e:*) {
					_playing = false;
				}
		}
		
		private function sampleData(e:SampleDataEvent):void {
			if (_playing) extract(e.data, BUFFER_SIZE);
			else silent(e.data, BUFFER_SIZE);
		}
		
		private function extract(target:ByteArray, length:int):void {
			var end:int = _loopEnd || _sound.length * 44.1 - BACK_DELAY;
			while(0 < length) {
				if(_position + length > end) {
					var read:int = end - _position;
					_sound.extract(target, read, _position + FRONT_DELAY);
					_position += read;
					length -= read;
				} else {
					_sound.extract(target, length, _position + FRONT_DELAY);
					_position += length;
					length = 0;
				}
				if (_position >= end) _position = _loopBegin - BACK_DELAY;
			}
		}

		private function silent( target:ByteArray, length:int ):void {
			target.position = 0;
			while( length-- ) {
				target.writeFloat(0);
				target.writeFloat(0);
			}
		}		
		
		private function playComplete(e:Event = null):void {
			_sound.removeEventListener(Event.SOUND_COMPLETE, playComplete);
			_track.stopSound(this, true);
		}
		
		public function pause():void {
			_position = _channel.position;
			close();
		}
		
		public function stop():void {
			_position = 0;
			close();
		}
		
		public function cancelFade():void {
			if (!_fadingOut) return;
			GameCore.instance.tween.kill(this);
			volume = _initVolume;
		}
		
		public function fadeOut(time:Number):void {
			trace("fadeOut");
			if (time > 0) {
				_initVolume = _volume;
				_fadingOut = true;
				GameCore.instance.tween.to(this, time, { volume:0, onComplete:fadeOutComplete } );
			}
			else fadeOutComplete();
		}
		private function fadeOutComplete():void {
			stop();
			_track.stopSound(this, true);
			if(!isNaN(_initVolume)) {
				volume = _initVolume;
				_initVolume = NaN;
				_fadingOut = false;
			}
		}
		
		private function close():void {
			_playing = false;
			_channel.stop();
			try { 
				_output.close(); 
			} catch (e:*) { };
			_output.removeEventListener(SampleDataEvent.SAMPLE_DATA, sampleData);
		}
		
		public function rewind():void {
			_position = 0;
			if (_playing) play();
		}
		
		public function seekTo(milliseconds:Number):void {
			_position = milliseconds;
			if (_playing) play();
		}
		
		public function updateVolume():void {
			_volumeAbs = _volume * _track.volumeAbs;
			_transform.volume = _volumeAbs;
			if (_channel) _channel.soundTransform = _transform;
		}
		
		public function get volume():Number { return _volume; }		
		public function set volume(value:Number):void {
			_volume = value;
			updateVolume();
		}
		
		public function get volumeAbs():Number { return _volumeAbs; }
		
		public function get track():SoundTrack { return _track; }		
		public function set track(value:SoundTrack):void {
			_track = value;
			_manager = _track ? _track.manager : null;
			updateVolume();
		}
		
		public function get name():String { return _name; }
		
		public function get manager():SoundManager { return _manager; }
		
		public function get sound():Sound { return _sound; }
		
		public function get channel():SoundChannel { return _channel; }
		
		public function get transform():SoundTransform { return _transform; }
		
		public function get playing():Boolean { return _playing; }		
		
		public function get looping():Boolean { return _looping; }
		
		public function get loopBegin():int { return _loopBegin; }		
		public function set loopBegin(value:int):void {
			_loopBegin = value;
		}
		
		public function get loopEnd():int { return _loopEnd; }		
		public function set loopEnd(value:int):void {
			_loopEnd = value;
		}
		
	}

}